﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

using System;
using System.Collections.Generic;
using Tesla.Graphics;
using D3D = SlimDX.Direct3D10;
using DXGI = SlimDX.DXGI;

namespace Tesla.Direct3D10.Graphics {
    /// <summary>
    /// Implementation for a Direct3D 10 level graphics adapter.
    /// </summary>
    public sealed class D3D10GraphicsAdapter : IGraphicsAdapter {
        internal D3D.Device _graphicsDevice;
        private DisplayModeCollection _displayModes;
        private int _adapterIndex;
        private string _desc;
        private int _devId;
        private int _rev;
        private int _subsystemId;
        private int _vendorId;

        internal int AdapterOrdinal {
            get {
                return _adapterIndex;
            }
        }

        /// <summary>
        /// Gets the collection of supported display modes.
        /// </summary>
        public DisplayModeCollection SupportedDisplayModes {
            get {
                return _displayModes;
            }
        }

        /// <summary>
        /// Gets the description of the device.
        /// </summary>
        public String Description {
            get {
                return _desc;
            }
        }

        /// <summary>
        /// Gets the device ID which identifies the particular chipset.
        /// </summary>
        public int DeviceId {
            get {
                return _devId;
            }
        }

        /// <summary>
        /// Gets the adapter's revision number for the particular chipset its associated with.
        /// </summary>
        public int Revision {
            get {
                return _rev;
            }
        }

        /// <summary>
        /// Gets the value that identifies the adapter's subsystem.
        /// </summary>
        public int SubSystemId {
            get {
                return _subsystemId;
            }
        }

        /// <summary>
        /// Gets the value that identifies that chipset's manufacturer.
        /// </summary>
        public int VendorId {
            get {
                return _vendorId;
            }
        }

        /// <summary>
        /// Gets if this is the default adapter, always the first adapter.
        /// </summary>
        public bool IsDefaultAdapter {
            get {
                return _adapterIndex == 0;
            }
        }

        /// <summary>
        /// Gets the maximum size of a Texture1D resource.
        /// </summary>
        public int MaximumTexture1DSize {
            get {
                return D3D.Resource.MaximumTexture1DSize;
            }
        }

        /// <summary>
        /// Gets the maximum size (U,V) of a Texture2D resource.
        /// </summary>
        public int MaximumTexture2DSize {
            get {
                return D3D.Resource.MaximumTexture2DSize;
            }
        }

        /// <summary>
        /// Gets the maximum size (U,V,W) of a Texture3D resource.
        /// </summary>
        public int MaximumTexture3DSize {
            get {
                return D3D.Resource.MaximumTexture3DSize;
            }
        }

        /// <summary>
        /// Gets the maximum size of a TextureCube resource.
        /// </summary>
        public int MaximumTextureCubeSize {
            get {
                return D3D.Resource.MaximumTextureCubeSize;
            }
        }

        /// <summary>
        /// Gets the maximum number of render targets that can be set to the
        /// device at once (MRT).
        /// </summary>
        public int MaximumMultiRenderTargets {
            get {
                return 8;
            }
        }

        /// <summary>
        /// Gets the max vertex streams.
        /// </summary>
        public int MaximumVertexStreams {
            get {
                return 16;
            }
        }

        /// <summary>
        /// Gets the max sampler states.
        /// </summary>
        public int MaximumSamplerStates {
            get {
                return 16;
            }
        }

        /// <summary>
        /// Gets the max vertex sampler states.
        /// </summary>
        public int MaximumVertexSamplerStates {
            get {
                return 4;
            }
        }

        /// <summary>
        /// Queries if the specified surface format is valid for texture resources.
        /// </summary>
        /// <param name="surfaceFormat">Surface format</param>
        /// <param name="texType">The texture type</param>
        /// <returns>True if valid, false otherwise</returns>
        public bool QueryTextureFormat(SurfaceFormat surfaceFormat, TextureDimensions texType) {
            DXGI.Format format = D3D10Helper.ToD3DSurfaceFormat(surfaceFormat);
            D3D.FormatSupport support = _graphicsDevice.CheckFormatSupport(format);
            switch(texType) {
                case TextureDimensions.One:
                    return (support & D3D.FormatSupport.Texture1D) == D3D.FormatSupport.Texture1D;
                case TextureDimensions.Two:
                    return (support & D3D.FormatSupport.Texture2D) == D3D.FormatSupport.Texture2D;
                case TextureDimensions.Three:
                    return (support & D3D.FormatSupport.Texture3D) == D3D.FormatSupport.Texture3D;
                case TextureDimensions.Cube:
                    return (support & D3D.FormatSupport.TextureCube) == D3D.FormatSupport.TextureCube;
            }
            return false;
        }

        /// <summary>
        /// Queries if the specified surface format is valid for vertex texture resources.
        /// </summary>
        /// <param name="surfaceFormat">Surface format</param>
        /// <param name="texType">The texture type</param>
        /// <returns>True if valid, false otherwise</returns>
        public bool QueryVertexTextureFormat(SurfaceFormat surfaceFormat, TextureDimensions texType) {
            //TODO: Is there an explict check for vertex texture formats? For now just mimic the regular format check.
            //Might have to check which formats support manually.
            return QueryTextureFormat(surfaceFormat, texType);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="D3D10GraphicsAdapter"/> class.
        /// </summary>
        /// <param name="graphicsDevice">The graphics device.</param>
        /// <param name="factory">The DXGI factory.</param>
        /// <param name="adapterIndex">Index of the adapter.</param>
        internal D3D10GraphicsAdapter(D3D.Device graphicsDevice, DXGI.Factory factory, int adapterIndex) {
            _graphicsDevice = graphicsDevice;
            _adapterIndex = adapterIndex;

            List<DisplayMode> modes = new List<DisplayMode>();

            DXGI.Adapter adapter = factory.GetAdapter(adapterIndex);
            if(adapter != null) {
                DXGI.AdapterDescription adDesc = adapter.Description;
                _desc = adDesc.Description;
                _devId = adDesc.DeviceId;
                _rev = adDesc.Revision;
                _subsystemId = adDesc.SubsystemId;
                _vendorId = adDesc.VendorId;

                DXGI.Output output = adapter.GetOutput(0);
                if(output != null) {
                    foreach(SurfaceFormat format in Enum.GetValues(typeof(SurfaceFormat))) {
                        try {
                            System.Collections.ObjectModel.ReadOnlyCollection<DXGI.ModeDescription> modeList = output.GetDisplayModeList(D3D10Helper.ToD3DSurfaceFormat(format), 0);
                            if(modeList == null) {
                                continue;
                            }   
                            foreach(DXGI.ModeDescription modeDesc in modeList) {
                                DisplayMode displayMode = new DisplayMode(modeDesc.Width, modeDesc.Height, format);
                                if(!modes.Contains(displayMode)) {
                                    modes.Add(displayMode);
                                }
                            }
                        } catch(Exception e) {
                            String s =  e.StackTrace;
                            continue;
                        }
                    }
                    _displayModes = new DisplayModeCollection(modes);
                    output.Dispose();
                }
                adapter.Dispose();
            }
        }

        /// <summary>
        /// Queries if the specified formats and sample counts are valid for the backbuffer.
        /// </summary>
        /// <param name="format">Surface format</param>
        /// <param name="depthFormat">Depth format</param>
        /// <param name="multiSampleCount">Sample count</param>
        /// <returns>True if a valid combination</returns>
        public bool QueryBackBufferFormat(SurfaceFormat format, DepthFormat depthFormat, int multiSampleCount) {
            return CheckSurfaceFormat(format, multiSampleCount) && CheckDepthFormat(depthFormat, multiSampleCount);
        }

        /// <summary>
        /// Queries if the specified format and sample counts are valid for a render target.
        /// </summary>
        /// <param name="format">Surface format</param>
        /// <param name="depthFormat">Depth format</param>
        /// <param name="multiSampleCount">Sample count</param>
        /// <returns>True if a valid combination</returns>
        public bool QueryRenderTargetFormat(SurfaceFormat format, DepthFormat depthFormat, int multiSampleCount) {
            return CheckSurfaceFormat(format, multiSampleCount) && CheckDepthFormat(depthFormat, multiSampleCount);
        }

        /// <summary>
        /// Queries for the number of multisample quality levels are supported for the sample count.
        /// </summary>
        /// <param name="format">Specified format</param>
        /// <param name="multiSamplecount">Sample count</param>
        /// <returns>Number of supported quality levels </returns>
        public int QueryMultiSampleQualityLevels(SurfaceFormat format, int multiSamplecount) {
            return _graphicsDevice.CheckMultisampleQualityLevels(D3D10Helper.ToD3DSurfaceFormat(format), multiSamplecount);
        }

        private bool CheckDepthFormat(DepthFormat depthFormat, int sampleCount) {
            if(depthFormat == DepthFormat.None) {
                return true;
            }
            DXGI.Format format = D3D10Helper.ToD3DDepthFormat(depthFormat);
            D3D.FormatSupport support = _graphicsDevice.CheckFormatSupport(format);
            if(sampleCount > 0) {
                bool correctSamples = CheckMultiSampleQuality(format, sampleCount);
                return correctSamples && ((support & D3D.FormatSupport.DepthStencil) == D3D.FormatSupport.DepthStencil) 
                    && ((support & D3D.FormatSupport.FormatMultisampleRenderTargetSupport) == D3D.FormatSupport.FormatMultisampleRenderTargetSupport);
            }
            return (support & D3D.FormatSupport.DepthStencil) == D3D.FormatSupport.DepthStencil;
        }

        private bool CheckSurfaceFormat(SurfaceFormat surfaceFormat, int sampleCount) {
            DXGI.Format format = D3D10Helper.ToD3DSurfaceFormat(surfaceFormat);
            D3D.FormatSupport support = _graphicsDevice.CheckFormatSupport(format);
            if(sampleCount > 0) {
                bool correctSamples = CheckMultiSampleQuality(format, sampleCount);
                return correctSamples && ((support & D3D.FormatSupport.RenderTarget) == D3D.FormatSupport.RenderTarget) 
                    && ((support & D3D.FormatSupport.FormatMultisampleRenderTargetSupport) == D3D.FormatSupport.FormatMultisampleRenderTargetSupport);
            }
            return (support & D3D.FormatSupport.RenderTarget) == D3D.FormatSupport.RenderTarget;
        }

        private bool CheckMultiSampleQuality(DXGI.Format format, int sampleCount) {
            return _graphicsDevice.CheckMultisampleQualityLevels(format, sampleCount) > 0;
        }
    }
}
